package com.jt.sys.service.realm;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.druid.util.StringUtils;
import com.jt.sys.dao.SysMenuDao;
import com.jt.sys.dao.SysRoleMenuDao;
import com.jt.sys.dao.SysUserDao;
import com.jt.sys.dao.SysUserRoleDao;
import com.jt.sys.entity.SysUser;

@Service//这个类也交给了spring去管理
public class ShiroUserRealm extends AuthorizingRealm {
	@Autowired
	private SysUserDao sysUserDao;
	@Autowired
	private SysUserRoleDao sysUserRoleDao;
	@Autowired
	private SysRoleMenuDao sysRoleMenuDao;
	@Autowired
	private SysMenuDao sysMenuDao;

	/**
	 * 设置凭证匹配器，保存密码用md5，匹配的时候也要用
	 */
	@Override
	public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
		// HashedCredentialsMatcher
		// cMatcher=(HashedCredentialsMatcher)credentialsMatcher;
		HashedCredentialsMatcher cMatcher = new HashedCredentialsMatcher();
		cMatcher.setHashAlgorithmName("MD5");
		super.setCredentialsMatcher(cMatcher);
	}
/**
 * 下面2个方法是实现该抽象类AuthorizingRealm必须重写的2个方法。
 */
	/**
	 * 执行认证操作时，此方法用于获取用户的认证信息 说明：此方法由认证管理器调用
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken/* 这是个接口 */ token)
			throws AuthenticationException {
		// token.getPrincipal();//
		// 1获取客户端提交用户信息
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;// token里面封装的从客户端传递过来的用户名密码
		String username = upToken.getUsername();
		// char[] password=upToken.getPassword();//这里密码得到的是数组
		// 2基于用户名从数据库查询用户信息（根据传递过来的用户名去数据库查找该用户名对应的用户信息）
		SysUser user = sysUserDao.findUserByUserName(username);

		// 3.校验用户信息（用户存在吗，如果返回的用户对象为空，说明数据库中不存在该用户）
		if (user == null)
			throw new AuthenticationException("用户名或密码不正确1");
		// 4。对象用户信息进行封装（需要数据库里面获取用户的密码，盐值，返回给认证管理器，认证管理器根据客户端传递过来的密码与盐值进行加密之后，再和数据库查询返回的密码进行比对）
		ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, // principal用户身份,这里也可以写一个用户名字
				user.getPassword(), // hashedCredentials,已经加密的密码
				credentialsSalt, // credentialsSalt盐值
				this.getName());// realmName real name
		return info;// 此对象返回给了认证管理器
	}
	/**
	 * 执行授权操作时，此方法用于获取用户的权限信息
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		/*
		 *  1、获取用户对象(此对象依赖于认证概时封装的用户类型，也就是说看上面用户认证阶段第4步对用户信息封装的时候
		 *  是怎么封装的,上面封装的时候user封装的是一个对象类型，那么这里我么get的时候得到的就是对象类型，如果
		 *  上面封装的是String类型【假如上面写的是user.getName()】，这里得到的也是String类型，)
		 */
		SysUser user = (SysUser) principals.getPrimaryPrincipal();
		Integer[] array={};
		// 2、基于用户id查找角色id
		List<Integer> list1 = sysUserRoleDao.findRoleIdsByUserId(user.getId());
		Integer[] i1 = list1.toArray(array);
		// 3、基于角色id查找菜单（资源）id
		List<Integer> list2 = sysRoleMenuDao.findMenuIdsByRoleId(i1);
		Integer[] i2 = list2.toArray(array);
		// 4、基于菜单（资源）id查找权限标识
		List<String> list3 = sysMenuDao.findPermissions(i2);
		// 5.封装权限信息(AuthorizationInfo)子类类型以simple开头
		Set<String> set = new HashSet<>();
		/*这个set集合我们之所以要去重和去空、去空串是因为底层要用set集合里面的串调用.equals方法和*/
		for (String str : list3) {
			if(!StringUtils.isEmpty(str)){//这个地方可以去除空和空串，set集合可以去重复，两者结合起来就可以去重和去空、去空串
				set.add(str);				
			}
		}
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		info.setStringPermissions(set);
		
		return info;
	}
}
